]> git.saurik.com Git - apple/security.git/blob - OSX/Keychain Circle Notification/KNAppDelegate.m
Security-59306.11.20.tar.gz
[apple/security.git] / OSX / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32 #import "notify.h"
33 #import <utilities/debugging.h>
34 #import <utilities/SecCFWrappers.h>
35 #import <utilities/SecXPCError.h>
36 #import <os/variant_private.h>
37
38 #import <Accounts/Accounts.h>
39 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
40 #import <AOSAccounts/MobileMePrefsCore.h>
41 #import <AOSAccounts/ACAccountStore+iCloudAccount.h>
42 #import <AOSAccounts/iCloudAccount.h>
43
44 #include <msgtracer_client.h>
45 #include <msgtracer_keys.h>
46 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
47 #import <ProtectedCloudStorage/CloudIdentity.h>
48 #import "CoreCDP/CDPFollowUpController.h"
49 #import "CoreCDP/CDPFollowUpContext.h"
50 #import <CoreCDP/CDPAccount.h>
51
52 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
53 static const NSString * const kKickedOutKey = @"KickedOut";
54 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
55 static const NSString * const kPasswordChangedOrTrustedDeviceChanged = @"TDorPasswordChanged";
56 static NSString *prefpane = @"/System/Library/PreferencePanes/iCloudPref.prefPane";
57 #define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"
58 #define kPublicKeyAvailable "com.apple.security.publickeyavailable"
59 static NSString *KeychainPCDetailsAEAction = @"AKPCDetailsAEAction";
60 bool _hasPostedAndStillInError = false;
61 bool _haveCheckedForICDPStatusOnceInCircle = false;
62 bool _isAccountICDP = false;
63
64 @implementation KNAppDelegate
65
66 static NSUserNotificationCenter *appropriateNotificationCenter()
67 {
68 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
69 type: _NSUserNotificationCenterTypeSystem];
70 }
71
72 static BOOL isErrorFromXPC(CFErrorRef error)
73 {
74 // Error due to XPC failure does not provide information about the circle.
75 if (error && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(error)))) {
76 return YES;
77 }
78 return NO;
79 }
80
81 static void PSKeychainSyncIsUsingICDP(void)
82 {
83 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
84 ACAccount *primaryiCloudAccount = nil;
85
86 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
87 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
88 }
89
90 NSString *dsid = primaryiCloudAccount.icaPersonID;
91 BOOL isICDPEnabled = NO;
92 if (dsid) {
93 isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid];
94 NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE");
95 } else {
96 NSLog(@"iCDP: no primary account");
97 }
98
99 _isAccountICDP = isICDPEnabled;
100 }
101
102 -(void) startFollowupKitRepair
103 {
104 NSError *localError = NULL;
105 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
106 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
107 [cdpd postFollowUpWithContext:context error:&localError ];
108 if(localError){
109 secnotice("kcn", "request to CoreCDP to follow up failed: %@", localError);
110 }
111 else{
112 secnotice("kcn", "CoreCDP handling follow up");
113 _hasPostedAndStillInError = false;
114 }
115 }
116
117 - (void) handleDismissedNotification
118 {
119 if(_isAccountICDP){
120 secnotice("kcn", "handling dismissed notification, would start a follow up");
121 [self startFollowupKitRepair];
122 }
123 else
124 secerror("unable to find primary account");
125 }
126
127 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
128 {
129 if (eventName == nil)
130 return;
131
132 secnotice("kcn", "notifyiCloudPreferencesAbout %@", eventName);
133
134 NSString *accountID = (__bridge_transfer NSString*)(MMCopyLoggedInAccountFromAccounts());
135 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
136 ACAccount *primaryiCloudAccount = nil;
137
138 if ([accountStore respondsToSelector:@selector(icaPrimaryAppleAccount)]){
139 primaryiCloudAccount = [accountStore icaPrimaryAppleAccount];
140 }
141
142 if(primaryiCloudAccount){
143 AEDesc aeDesc;
144 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, accountID, &aeDesc);
145 if (createdAEDesc) {
146 NSArray *prefPaneURL = [NSArray arrayWithObject: [NSURL fileURLWithPath: prefpane ]];
147
148 LSLaunchURLSpec lsSpec = {
149 .appURL = NULL,
150 .itemURLs = (__bridge CFArrayRef)prefPaneURL,
151 .passThruParams = &aeDesc,
152 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
153 .asyncRefCon = NULL,
154 };
155
156 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
157
158 if (err)
159 secerror("Can't send event %@, err=%d", eventName, err);
160 AEDisposeDesc(&aeDesc);
161 } else {
162 secerror("unable to create and send aedesc for account: '%@' and action: '%@'\n", primaryiCloudAccount, eventName);
163 }
164 }
165 secerror("unable to find primary account");
166 }
167
168 - (void) timerCheck
169 {
170 NSDate *nowish = [NSDate new];
171
172 self.state = [KNPersistentState loadFromStorage];
173 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
174 secnotice("kcn", "REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
175
176 // self.circle.rawStatus might not be valid yet
177 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
178 // Still have a request pending, send reminder, and also in addtion to the UI
179 // we need to send a notification for iCloud pref pane to pick up
180 CFNotificationCenterPostNotificationWithOptions(
181 CFNotificationCenterGetDistributedCenter(),
182 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
183 (__bridge const void *) [self.state.applicationDate description], NULL, 0
184 );
185
186 [self postApplicationReminder];
187 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
188 [self.state writeToStorage];
189 }
190 }
191 }
192
193
194 - (void) scheduleActivityAt: (NSDate *) time
195 {
196 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
197 NSTimeInterval howSoon = [time timeIntervalSinceNow];
198 if (howSoon > 0)
199 [self scheduleActivityIn:ceil(howSoon)];
200 else
201 [self timerCheck];
202 }
203 }
204
205
206 - (void) scheduleActivityIn: (int) alertInterval
207 {
208 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
209 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
210 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
211 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
212 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
213 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
214
215 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
216 [self timerCheck];
217 });
218 }
219
220
221 - (NSTimeInterval) getPendingApplicationReminderInterval
222 {
223 if (self.state.pendingApplicationReminderInterval)
224 return [self.state.pendingApplicationReminderInterval doubleValue];
225 else
226 return 24*60*60;
227 }
228
229 - (NSMutableSet *) makeApplicantSet {
230 KNAppDelegate *me = self;
231 NSMutableSet *applicantIds = [NSMutableSet new];
232 for (KDCirclePeer *applicant in me.circle.applicants) {
233 [me postForApplicant:applicant];
234 [applicantIds addObject:applicant.idString];
235 }
236 return applicantIds;
237 }
238
239 - (bool) removeAllNotificationsOfType: (NSString *) typeString {
240 bool didRemove = false;
241 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
242 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
243 if (note.userInfo[typeString]) {
244 [appropriateNotificationCenter() removeDeliveredNotification: note];
245 didRemove = true;
246 }
247 }
248 return didRemove;
249 }
250
251 static const char *sosCCStatusCString(SOSCCStatus status) {
252 switch(status) {
253 case kSOSCCError: return "kSOSCCError";
254 case kSOSCCInCircle: return "kSOSCCInCircle";
255 case kSOSCCNotInCircle: return "kSOSCCNotInCircle";
256 case kSOSCCCircleAbsent: return "kSOSCCCircleAbsent";
257 case kSOSCCRequestPending: return "kSOSCCRequestPending";
258 default: return "unknown";
259 }
260 }
261
262 static const char *sosDepartureReasonCString(enum DepartureReason departureReason){
263 switch(departureReason) {
264 case kSOSDepartureReasonError: return "kSOSDepartureReasonError";
265 case kSOSNeverLeftCircle: return "kSOSNeverLeftCircle";
266 case kSOSWithdrewMembership: return "kSOSWithdrewMembership";
267 case kSOSMembershipRevoked: return "kSOSMembershipRevoked";
268 case kSOSLeftUntrustedCircle: return "kSOSLeftUntrustedCircle";
269 case kSOSNeverAppliedToCircle: return "kSOSNeverAppliedToCircle";
270 case kSOSDiscoveredRetirement: return "kSOSDiscoveredRetirement";
271 case kSOSLostPrivateKey: return "kSOSLostPrivateKey";
272 default: return "unknown reason";
273 }
274 }
275
276
277 - (void) processCircleState {
278 CFErrorRef err = NULL;
279 KNAppDelegate *me = self;
280
281 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&err);
282 if (isErrorFromXPC(err)) {
283 secnotice("kcn", "SOSCCGetLastDepartureReason failed with xpc error: %@", err);
284 CFReleaseNull(err);
285 return;
286 } else if (err) {
287 secnotice("kcn", "SOSCCGetLastDepartureReason failed with: %@", err);
288 }
289 CFReleaseNull(err);
290
291 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&err);
292 if (isErrorFromXPC(err)) {
293 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with xpc error: %@", err);
294 CFReleaseNull(err);
295 return;
296 } else if (err) {
297 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
298 }
299 CFReleaseNull(err);
300
301 NSDate *nowish = [NSDate date];
302 me.state = [KNPersistentState loadFromStorage];
303 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
304
305 PSKeychainSyncIsUsingICDP();
306
307 secnotice("kcn", "processCircleState starting ICDP: %s SOSCCStatus: %s DepartureReason: %s",
308 (_isAccountICDP) ? "Enabled": "Disabled",
309 sosCCStatusCString(circleStatus),
310 sosDepartureReasonCString(departureReason));
311
312 if(_isAccountICDP){
313 me.state.lastCircleStatus = circleStatus;
314 [me.state writeToStorage];
315
316 switch(circleStatus) {
317 case kSOSCCInCircle:
318 secnotice("kcn", "iCDP: device is in circle!");
319 _hasPostedAndStillInError = false;
320 break;
321 case kSOSCCRequestPending:
322 [me scheduleActivityAt:me.state.pendingApplicationReminder];
323 break;
324 case kSOSCCCircleAbsent:
325 case kSOSCCNotInCircle:
326 [me outOfCircleAlert: departureReason];
327 secnotice("kcn", "{ChangeCallback} Pending request START");
328 me.state.applicationDate = nowish;
329 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
330 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
331 [me scheduleActivityAt:me.state.pendingApplicationReminder];
332 break;
333 case kSOSCCError:
334 /*
335 You would think we could count on not being iCDP if the account was signed out. Evidently that's wrong.
336 So we'll go based on the artifact that when the account object is reset (like by signing out) the
337 departureReason will be set to kSOSDepartureReasonError. So we won't push to get back into a circle if that's
338 the current reason. I've checked code for other ways we could be out. If we boot and can't load the account
339 we'll end up with kSOSDepartureReasonError. Then too if we end up in kSOSDepartureReasonError and reboot we end up
340 in the same place. Leave it to cdpd to decide whether the user needs to sign in to an account.
341 */
342 if(departureReason != kSOSDepartureReasonError) {
343 secnotice("kcn", "ICDP: We need the password to initiate trust");
344 [me postRequirePassword];
345 _hasPostedAndStillInError = true;
346 } else {
347 secnotice("kcn", "iCDP: We appear to not be associated with an iCloud account");
348 }
349 break;
350 default:
351 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
352 break;
353 }
354 } else { // SA version
355 switch(circleStatus) {
356 case kSOSCCInCircle:
357 secnotice("kcn", "SA: device is in circle!");
358 _hasPostedAndStillInError = false;
359 break;
360 case kSOSCCRequestPending:
361 [me scheduleActivityAt:me.state.pendingApplicationReminder];
362 secnotice("kcn", "{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
363 break;
364 case kSOSCCCircleAbsent:
365 case kSOSCCNotInCircle:
366 switch (departureReason) {
367 case kSOSDiscoveredRetirement:
368 case kSOSLostPrivateKey:
369 case kSOSWithdrewMembership:
370 case kSOSNeverAppliedToCircle:
371 case kSOSDepartureReasonError:
372 case kSOSMembershipRevoked:
373 default:
374 if(me.state.lastCircleStatus == kSOSCCInCircle) {
375 secnotice("kcn", "SA: circle status went from in circle to %s: reason: %s", sosCCStatusCString(circleStatus), sosDepartureReasonCString(departureReason));
376 }
377 break;
378
379 case kSOSNeverLeftCircle:
380 case kSOSLeftUntrustedCircle:
381 [me outOfCircleAlert: departureReason];
382 secnotice("kcn", "{ChangeCallback} Pending request START");
383 me.state.applicationDate = nowish;
384 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
385 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
386 [me scheduleActivityAt:me.state.pendingApplicationReminder];
387 break;
388 }
389 break;
390 case kSOSCCError:
391 if(me.state.lastCircleStatus == kSOSCCInCircle && (departureReason == kSOSNeverLeftCircle)) {
392 secnotice("kcn", "SA: circle status went from in circle to error - we need the password");
393 [me postRequirePassword];
394 _hasPostedAndStillInError = true;
395 }
396 break;
397 default:
398 secnotice("kcn", "Bad SOSCCStatus return %d", circleStatus);
399 break;
400 }
401 }
402
403
404 // Circle applications: pending request(s) started / completed
405 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
406 secnotice("kcn", "Pending request completed");
407 me.state.applicationDate = [NSDate distantPast];
408 me.state.pendingApplicationReminder = [NSDate distantFuture];
409 [me.state writeToStorage];
410
411 // Remove reminders
412 if([me removeAllNotificationsOfType: @"ApplicationReminder"]) {
413 secnotice("kcn", "{ChangeCallback} removed application remoinders");
414 }
415 }
416
417 // Clear out (old) reset notifications
418 if (circleStatus == kSOSCCInCircle) {
419 secnotice("kcn", "{ChangeCallback} kSOSCCInCircle");
420 if([me removeAllNotificationsOfType: (NSString*) kValidOnlyOutOfCircleKey]) {
421 secnotice("kcn", "Removed existing notifications now that we're in circle");
422 }
423 if([me removeAllNotificationsOfType: (NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
424 secnotice("kcn", "Removed existing password notifications now that we're in circle");
425 }
426
427 // Applicants
428 secnotice("kcn", "{ChangeCallback} Applicants");
429 NSMutableSet *applicantIds = [me makeApplicantSet];
430 // Clear applicant notifications that aren't pending any more
431 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
432 secnotice("kcn", "Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
433 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
434 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
435 secnotice("kcn", "No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
436 [notificationCenter removeDeliveredNotification:note];
437 } else {
438 secnotice("kcn", "Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
439 }
440 }
441 } else { // Clear any pending applicant notifications since we aren't in circle or invalid
442 if([me removeAllNotificationsOfType: (NSString*) @"applicantId"]) {
443 secnotice("kcn", "Not in circle or invalid - removed applicant notes");
444 }
445 }
446
447 me.state.lastCircleStatus = circleStatus;
448 [me.state writeToStorage];
449 }
450
451 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
452 {
453 appropriateNotificationCenter().delegate = self;
454 int out_taken;
455 int available;
456 secnotice("kcn", "Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
457
458 notify_register_dispatch(kPublicKeyAvailable, &available, dispatch_get_main_queue(), ^(int token) {
459 CFErrorRef err = NULL;
460 KNAppDelegate *me = self;
461 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&err);
462
463 if (isErrorFromXPC(err)) {
464 secnotice("kcn", "SOSCCThisDeviceIsInCircle failed with: %@", err);
465 CFReleaseNull(err);
466 return;
467 }
468 CFReleaseNull(err);
469
470 me.state = [KNPersistentState loadFromStorage];
471
472 secnotice("kcn", "got public key available notification");
473 me.state.lastCircleStatus = currentCircleStatus;
474 [me.state writeToStorage];
475 });
476
477 //register for public key not available notification, if occurs KCN can react
478 notify_register_dispatch(kPublicKeyNotAvailable, &out_taken, dispatch_get_main_queue(), ^(int token) {
479 secnotice("kcn", "got public key not available notification");
480 KNAppDelegate *me = self;
481 [me processCircleState];
482 });
483
484 self.viewedIds = [NSMutableSet new];
485 self.circle = [KDSecCircle new];
486 KNAppDelegate *me = self;
487
488 [self.circle addChangeCallback:^{
489 secnotice("kcn", "{ChangeCallback}");
490 [me processCircleState];
491 }];
492 }
493
494
495 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
496 shouldPresentNotification: (NSUserNotification *) notification
497 {
498 return YES;
499 }
500
501
502 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
503 didActivateNotification: (NSUserNotification *) notification
504 {
505 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
506 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
507 }
508 }
509
510
511 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
512 didDismissAlert: (NSUserNotification *) notification
513 {
514 [self handleDismissedNotification];
515
516 // If we don't do anything here & another notification comes in we
517 // will repost the alert, which will be dumb.
518 id applicantId = notification.userInfo[@"applicantId"];
519 if (applicantId != nil) {
520 [self.viewedIds addObject:applicantId];
521 }
522 }
523
524
525 - (void) postForApplicant: (KDCirclePeer *) applicant
526 {
527 static int postCount = 0;
528
529 if ([self.viewedIds containsObject:applicant.idString]) {
530 secnotice("kcn", "Already viewed %@, skipping", applicant);
531 return;
532 }
533
534 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
535 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
536 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
537 if (note.isPresented) {
538 secnotice("kcn", "Already posted&presented: %@ (I=%@)", note, note.userInfo);
539 return;
540 } else {
541 secnotice("kcn", "Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
542 }
543 }
544 }
545
546 NSUserNotification *note = [NSUserNotification new];
547 note.title = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicant.name];
548 note.informativeText = [KNAppDelegate localisedApprovalBodyWithDeviceTypeFromPeerInfo:applicant.peerObject];
549 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
550 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
551 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
552 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE);
553 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE);
554 note.identifier = [[NSUUID new] UUIDString];
555 note.userInfo = @{
556 @"applicantName": applicant.name,
557 @"applicantId" : applicant.idString,
558 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
559 };
560
561 secnotice("kcn", "About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
562 [appropriateNotificationCenter() deliverNotification:note];
563 [self.viewedIds addObject:applicant.idString];
564 postCount++;
565 }
566
567 + (NSString *)localisedApprovalBodyWithDeviceTypeFromPeerInfo:(id)peerInfo {
568 NSString *type = (__bridge NSString *)SOSPeerInfoGetPeerDeviceType((__bridge SOSPeerInfoRef)(peerInfo));
569 CFStringRef localisedType = NULL;
570 if ([type isEqualToString:@"iPad"]) {
571 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPAD);
572 } else if ([type isEqualToString:@"iPhone"]) {
573 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPHONE);
574 } else if ([type isEqualToString:@"iPod"]) {
575 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_IPOD);
576 } else if ([type isEqualToString:@"Mac"]) {
577 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_MAC);
578 } else {
579 localisedType = SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX_GENERIC);
580 }
581 return (__bridge_transfer NSString *)localisedType;
582 }
583
584 - (void) postRequirePassword
585 {
586 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(NULL);
587 if(currentCircleStatus != kSOSCCError) {
588 secnotice("kcn", "postRequirePassword when not needed");
589 return;
590 }
591
592 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(NULL);
593
594 if(_isAccountICDP){
595 secnotice("kcn","would have posted needs password and then followed up");
596 [self startFollowupKitRepair];
597 } else if(departureReason == kSOSNeverLeftCircle) { // The only SA case for prompting
598 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
599 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
600 if (note.userInfo[(NSString*) kPasswordChangedOrTrustedDeviceChanged]) {
601 if (note.isPresented) {
602 secnotice("kcn", "Already posted & presented: %@", note);
603 [appropriateNotificationCenter() removeDeliveredNotification: note];
604 } else {
605 secnotice("kcn", "Already posted, but not presented: %@", note);
606 }
607 }
608 }
609
610 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
611 if (os_variant_has_internal_ui("iCloudKeychain")) {
612 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), "Device became untrusted or password changed"];
613 message = [message stringByAppendingString: reason_str];
614 }
615
616 NSUserNotification *note = [NSUserNotification new];
617 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
618 note.informativeText = message;
619 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
620 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
621 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
622 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
623 note.identifier = [[NSUUID new] UUIDString];
624
625 note.userInfo = @{
626 kPasswordChangedOrTrustedDeviceChanged : @1,
627 @"Activate" : (__bridge NSString *) kMMPropertyKeychainPCDetailsAEAction,
628 };
629
630 secnotice("kcn", "body=%@", note.informativeText);
631 secnotice("kcn", "About to post #-/%lu (PASSWORD/TRUSTED DEVICE): %@", noteCenter.deliveredNotifications.count, note);
632 [appropriateNotificationCenter() deliverNotification:note];
633 } else {
634 secnotice("kcn", "postRequirePassword when not needed for SA");
635 }
636 }
637
638 - (void) outOfCircleAlert: (int) reason
639 {
640
641 if(!_isAccountICDP){
642 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
643 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
644 if (note.userInfo[(NSString*) kKickedOutKey]) {
645 if (note.isPresented) {
646 secnotice("kcn", "Already posted&presented (removing): %@", note);
647 [appropriateNotificationCenter() removeDeliveredNotification: note];
648 } else {
649 secnotice("kcn", "Already posted, but not presented: %@", note);
650 }
651 }
652 }
653
654 NSString *message = CFBridgingRelease(SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX));
655 if (os_variant_has_internal_ui("iCloudKeychain")) {
656 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), sosDepartureReasonCString(reason)];
657 message = [message stringByAppendingString: reason_str];
658 }
659
660 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
661 // Contrary to HI spec (and I think it makes more sense)
662 // 1. otherButton == top : Not Now
663 // 2. actionButton == bottom: Continue
664 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
665 NSUserNotification *note = [NSUserNotification new];
666 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
667 note.informativeText = message;
668 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
669 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
670 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
671 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
672 note.identifier = [[NSUUID new] UUIDString];
673
674 note.userInfo = @{
675 kKickedOutKey : @1,
676 kValidOnlyOutOfCircleKey: @1,
677 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
678 };
679
680 secnotice("kcn", "body=%@", note.informativeText);
681 secnotice("kcn", "About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
682 [appropriateNotificationCenter() deliverNotification:note];
683 }
684
685 else{
686 secnotice("kcn","outOfCircleAlert starting followup repair");
687 [self startFollowupKitRepair];
688 }
689 }
690
691 - (void) postApplicationReminder
692 {
693 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
694 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
695 if (note.userInfo[@"ApplicationReminder"]) {
696 if (note.isPresented) {
697 secnotice("kcn", "Already posted&presented (removing): %@", note);
698 [appropriateNotificationCenter() removeDeliveredNotification: note];
699 } else {
700 secnotice("kcn", "Already posted, but not presented: %@", note);
701 }
702 }
703 }
704
705 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
706 // Contrary to HI spec (and I think it makes more sense)
707 // 1. otherButton == top : Not Now
708 // 2. actionButton == bottom: Continue
709 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
710 NSUserNotification *note = [NSUserNotification new];
711 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
712 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
713 note._identityImage = [NSImage bundleImageNamed:kAOSUISpyglassAppleID];
714 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
715 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
716 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
717 note.identifier = [[NSUUID new] UUIDString];
718
719 note.userInfo = @{
720 @"ApplicationReminder" : @1,
721 kValidOnlyOutOfCircleKey: @1,
722 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
723 };
724
725 secnotice("kcn", "About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
726 [appropriateNotificationCenter() deliverNotification:note];
727 }
728
729 @end